package de.uni.muenster.idw2.location.bluetooth.service.transformations;

/* Class for performing the Gauss Jordan Algorithm for solving equations. 
 * Code taken (only for this class)from http://ic.ucsc.edu/~ptantalo/math21/Winter07/GaussJordan.java
 * and modified for our specific purposes. It as eliminated most of the IO stuff 
 * 
 * */
class GaussJordan {
	// swap()
	// swap row i with row k
	// pre: A[i][q]==A[k][q]==0 for 1<=q<j
	void swap(double[][] A, int i, int k, int j) {
		int m = A[0].length - 1;
		double temp;
		for (int q = j; q <= m; q++) {
			temp = A[i][q];
			A[i][q] = A[k][q];
			A[k][q] = temp;
		}
	}

	// divide()
	// divide row i by A[i][j]
	// pre: A[i][j]!=0, A[i][q]==0 for 1<=q<j
	// post: A[i][j]==1;
	void divide(double[][] A, int i, int j) {
		int m = A[0].length - 1;
		for (int q = j + 1; q <= m; q++)
			A[i][q] /= A[i][j];
		A[i][j] = 1;
	}

	// eliminate()
	// subtract an appropriate multiple of row i from every other row
	// pre: A[i][j]==1, A[i][q]==0 for 1<=q<j
	// post: A[p][j]==0 for p!=i
	void eliminate(double[][] A, int i, int j) {
		int n = A.length - 1;
		int m = A[0].length - 1;
		for (int p = 1; p <= n; p++) {
			if (p != i && A[p][j] != 0) {
				for (int q = j + 1; q <= m; q++) {
					A[p][q] -= A[p][j] * A[i][q];
				}
				A[p][j] = 0;
			}
		}
	}

	// printMatrix()
	// print the present state of Matrix A to file out
	/*
	 * static void printMatrix(PrintWriter out, double[][] A){ int n =
	 * A.length - 1; int m = A[0].length - 1; for(int i=1; i<=n; i++){
	 * for(int j=1; j<=m; j++) out.print(A[i][j] + "  "); out.println(); }
	 * out.println(); out.println(); }
	 */

	// main()
	// read input file, initialize matrix, perform Gauss-Jordan Elimination,
	// and write resulting matrices to output file
	public double[]/* static void main(String[] args) throws IOException */Solve(
			double[][] extendedMatrix) {
		int n, m, i, j, k;

		n = extendedMatrix.length;
		m = extendedMatrix[0].length;

		// declare A to be of size (n+1)x(m+1) and do not use index 0
		double[][] A = new double[n + 1][m + 1];

		// read next n lines of input file and initialize array A
		for (i = 1; i <= n; i++) {
			for (j = 1; j <= m; j++) {
				A[i][j] = extendedMatrix[i - 1][j - 1];
			}
		}
		// perform Gauss-Jordan Elimination algorithm
		i = 1;
		j = 1;
		while (i <= n && j <= m) {

			// look for a non-zero entry in col j at or below row i
			k = i;
			while (k <= n && A[k][j] == 0)
				k++;

			// if such an entry is found at row k
			if (k <= n) {

				// if k is not i, then swap row i with row k
				if (k != i) {
					swap(A, i, k, j);
					// printMatrix(out, A);
				}

				// if A[i][j] is not 1, then divide row i by A[i][j]
				if (A[i][j] != 1) {
					divide(A, i, j);
					// printMatrix(out, A);
				}

				// eliminate all other non-zero entries from col j by
				// subtracting from each
				// row (other than i) an appropriate multiple of row i
				eliminate(A, i, j);
				// printMatrix(out, A);
				i++;
			}
			j++;
		}

		// for filling the results...
		double[] result = new double[A.length - 1];
		for (int dim = 0; dim < result.length; dim++) {
			result[dim] = A[dim + 1][A[0].length - 1];
		}
		return result;

	}
}

public class Similarity {
	
	/**
	 * The region code for Germany: <country telephone code>00
	 */
	private static final int REGION_GERMANY = 4900;
	
	public static SimilarityTransformer similarityAlgorithm(double xsource1, double ysource1,
			double xsource2, double ysource2, double xtarget1,
			double ytarget1, double xtarget2, double ytarget2){
		SimilarityTransformer transformer = new SimilarityTransformer( 
				Similarity.similarityAlg(xsource1, ysource1,
				xsource2,  ysource2, xtarget1,
				ytarget1, xtarget2, ytarget2));
		return transformer;
		 
	}
	
	private static double[] similarityAlg(double xsource1, double ysource1,
			double xsource2, double ysource2, double xtarget1,
			double ytarget1, double xtarget2, double ytarget2) {
		// similarity = function(xsource1, ysource1, xsource2, ysource2,
		// xtarget1, ytarget1, xtarget2, ytarget2){
		/*
		 * #equations are in the following way #Xt1 = 1*a0 + 0*b0 + a* Xs1 +
		 * b*Ys1 #Yt1 = 0*a0 + 1*b0 + a* Ys1 - b*Xs1 #Xt2 = 1*a0 + 0*b0 + a*
		 * Xs2 + b*Ys2 #Yt2 = 0*a0 + 1*b0 + a* Ys2 - b*Xs2
		 * 
		 * #in order to express the equations we have the following matrix
		 */
		// equations = matrix(c(1,0,1,0, 0,1,0,1, xsource1, ysource1,
		// xsource2, ysource2, ysource1, -1*xsource1 , ysource2 ,
		// -1*xsource2),4);
		double[][] equations = new double[][] {
				{ 1, 0, xsource1, ysource1, xtarget1 },
				{ 0, 1, ysource1, -1 * xsource1, ytarget1 },
				{ 1, 0, xsource2, ysource2, xtarget2 },
				{ 0, 1, ysource2, -1 * xsource2, ytarget2 } };

		// attached to the last
		// rvector = c(xtarget1, ytarget1, xtarget2, ytarget2);

		// results = solve(equations, rvector);
		GaussJordan gaussJordan = new GaussJordan();
		double[] results = gaussJordan.Solve(equations);
		/*
		 * #the order of the results is going to be: a0, b0, a, b. a0 =
		 * results [1][1]; b0 = results [2][1]; a = results [3][1]; b =
		 * results [4][1];
		 */
		/*
		 * double a0 = results [0]; double b0 = results [1]; double a =
		 * results [2]; double b = results [3];
		 */

		/*
		 * scalef = sqrt(a^2 + b^2); angle = atan(b/a);
		 * 
		 * #results #a0, a, b, b0 #scale factor (scalef), rotation angle
		 * (angle)
		 * 
		 * retval = list(a0 = a0, a=a, b =b, b0 = b0, scalef = scalef, angle
		 * = angle, angleDeg = rad2deg(angle), equations);
		 */
		return results;
	}
    
	/**
	 * This method calculates the parameters of a similarity transformation by using as a basis 
	 * the points in the source coordinate system WGS84. The logic behind uses as a projected coordinate the Gauss-Krueger
	 * projection. For performing the transformation should be used the method SimilarityTransformer.transformUsingSimilarityFromWGS84
	 * using the object returned by this method.
	 * @param wgs84xsource1 The longitude of the first reference point
	 * @param wgs84ysource1 The latitude of the first reference point
	 * @param wgs84xsource2 The longitude of the second reference point
	 * @param wgs84ysource2 The latitude of the second reference point
	 * @param xtarget1 The x component of the first reference (target) point
	 * @param ytarget1 The y component of the first reference (target) point
	 * @param xtarget2 The x component of the second reference (target) point
	 * @param ytarget2 The y component of the second reference (target) point
	 * @return Parameters for the further transformations
	 */
	public static SimilarityTransformer similarityAlgorithmFromWGS84(double wgs84xsource1long, double wgs84ysource1lat,
																	 double wgs84xsource2long, double wgs84ysource2lat, 
																	 double xtarget1, double ytarget1, 
																	 double xtarget2, double ytarget2) {
		double xsource1, ysource1,xsource2, ysource2;
		GkPoint point = TransformCoordinates.wgs84ToGermanGk(wgs84ysource1lat, wgs84xsource1long);
		xsource1 = point.getNorthing(); //latitude
		ysource1 = point.getRawEasting(); //long
		point =  TransformCoordinates.wgs84ToGermanGk(wgs84ysource2lat, wgs84xsource2long);
		xsource2 = point.getNorthing(); //latitude
		ysource2 = point.getRawEasting(); //long
		
		SimilarityTransformer transformer = new SimilarityTransformer( 
				Similarity.similarityAlg(xsource1, ysource1,
				xsource2,  ysource2, xtarget1,
				ytarget1, xtarget2, ytarget2));
		
		return transformer;
	}
	
	/**
	 * This method calculates the parameters of a similarity transformation by using as a basis 
	 * the points in the target coordinate system WGS84. The logic behind uses as a projected coordinate the Gauss-Krueger
	 * projection. For performing the transformation should be used the method SimilarityTransformer.transformUsingSimilarityFromWGS84
	 * using the object returned by this method.
	 * 
	 * This method is only applicable for Germany.
	 *
	 * @param xsource_1 The x component of the first reference (source) point
	 * @param ysource1 The y component of the first reference (source) point
	 * @param xsource2 The x component of the second reference (source) point
	 * @param ysource2 The y component of the second reference (source) point
	 * @param wgs84xsource1 The longitude of the first reference point
	 * @param wgs84ysource1 The latitude of the first reference point
	 * @param wgs84xsource2 The longitude of the second reference point
	 * @param wgs84ysource2 The latitude of the second reference point
	 * @return Parameters for the further transformations
	 */
	public static SimilarityTransformer similarityAlgorithmToWGS84inGermany(double xsource_1, double ysource1, 
																   double xsource2, double ysource2, 
																   double wgs84xtarget1long, double wgs84ytarget1lat,
																   double wgs84xtarget2long, double wgs84ytarget2lat) {
		double xtarget1, ytarget1, xtarget2, ytarget2;
		GkPoint point = TransformCoordinates.wgs84ToGermanGk(wgs84ytarget1lat, wgs84xtarget1long);
		xtarget1 = point.getNorthing();
		ytarget1 = point.getGkEasting(REGION_GERMANY);
		
		point =  TransformCoordinates.wgs84ToGermanGk(wgs84ytarget2lat, wgs84xtarget2long);
		xtarget2 = point.getNorthing();
		ytarget2 = point.getGkEasting(REGION_GERMANY);
		
		SimilarityTransformer transformer = new SimilarityTransformer( 
				Similarity.similarityAlg(
						xsource_1, ysource1, 
						xsource2, ysource2,
						xtarget1, ytarget1,
						xtarget2,  ytarget2 
						));
		
		return transformer;
	}
	
	static ResultPoint transformWithSimilarity(double[] results, double xsource,
			double ysource) {
		// transformWithSimilarity = function(similarityResult, xsource,
		// ysource)
		double a0 = results[0];
		double b0 = results[1];
		double a = results[2];
		double b = results[3];

        // #equations for the similarity method (extended version)
		double xtarget = 1 * a0 + 0 * b0 + a * xsource + b * ysource;
		double ytarget = 0 * a0 + 1 * b0 + a * ysource - b * xsource;
		
		return new ResultPoint(xtarget, ytarget);
		// retval = list(xtarget = xtarget, ytarget = ytarget);
	}		
	
	public static void testSimilarity(){
		SimilarityTransformer.testSimilarity();
	}
}

class ResultPoint {
   double x;
   double y;
   ResultPoint(double x, double y){
	   this.x = x;
	   this.y = y;
   }
   public double getX(){
	   return x;
   } 
   public double getY(){
	   return y;
   } 
   
   @Override
   public String toString(){
	   return "x:" + x + " y:" + y;  
   } 
}

